#include "CShip.h"
#include "MainLayer.h"
#include "GamePlayScene.h"
#include "CSoundPlayer.h"
USING_NS_CC;

CShip::CShip() :	
	__speed(0.0f),
	__winSize(),
	__move(),
	__pMoveAction(NULL),
	__pEngineMoveAction(NULL),
	__pShieldMoveAction(NULL),
	__pShield(NULL),
	__pEngine(NULL),
	__currentShipDirection(),
	__pRestoreSpriteAction(NULL),
	__pBullets(NULL),
	__invulnerable(false),
	__shieldPointsBase(0.0f),
	__healthPointsBase(0.0f),
	__currentShieldPoints(0.0f),
	__currentHealthPoints(0.0f),
	__gunType(CBulletParams::standard),
	__bulletParams(),
	__standardGunInterval(0.0f),
	__bombGunInterval(0.0f)
{}

CShip::~CShip()
{
	delete __pShield;
	delete __pEngine;
	if(__pBullets)
	{
		__pBullets->removeAllObjects();
		__pBullets->release();
	}
}

/**
 * Initialize the ship, engine and shield objects.
 *
 * @param frameNamePrefix The frame prefix for the ship sprite.
 * @param pSpriteSheet This parameter is created and released by the MainLayer.
 *
 * @return true if sprites were initialized without problems, false otherwise.
 */
bool
CShip::init(char* frameNamePrefix, cocos2d::CCSpriteBatchNode* pSpriteSheet, b2World* pWorld)
{
	if(!initEngineAndShield(frameNamePrefix, pSpriteSheet, pWorld))
	{
		return false;
	}

	CAnimationProperties* rollAnim = new CAnimationProperties();
	rollAnim->_animationName = "shipRoll";
	rollAnim->_frameCount = 14;
	rollAnim->_delay = 0.07f;
	CAnimatedObject::addAnimation(rollAnim);

	__invulnerable = false;
	__currentShipDirection = Normal;
	__speed = 1000.0f;
	__shieldPointsBase = 500.0;
	__healthPointsBase = 500.0;
	__currentShieldPoints = __shieldPointsBase;
	__currentHealthPoints = __healthPointsBase;
	__pBullets = new CCArray();

	__bulletParams._speed = 1400;
	__bulletParams._hits = 1;
	__bulletParams._type = CBulletParams::standard;

	__standardGunInterval = 0.3f;
	__bombGunInterval = 1.0f;

	this->setTag(MainLayer::shipTag);

	__winSize = CCDirector::sharedDirector()->getWinSize();
	this->setPosition(ccp(__winSize.width / 2, __winSize.height / 2));

	CCScene* scene = CCDirector::sharedDirector()->getRunningScene();
	MainLayer* mainLayer = static_cast<MainLayer*>(scene->getChildByTag(GamePlayScene::mainLayerTag));
	mainLayer->updateHealthPoints(100.0f);
	mainLayer->updateShieldPoints(100.0f);

	createBoxBody();

	schedule(schedule_selector(CShip::move), 0.03f);

	return true;
}

/**
 * @return true if sprites were initialized without problems, false otherwise.
 */
bool
CShip::initEngineAndShield(char* frameNamePrefix, CCSpriteBatchNode* pSpriteSheet, b2World* pWorld)
{
	if(!CAnimatedObject::init(frameNamePrefix, pSpriteSheet, pWorld))
	{
		return false;
	}

	__pEngine = new CEngine();
	if(!__pEngine->init("shipEngineNormal", pSpriteSheet, pWorld))
	{
		return false;
	}
	else
	{
		pSpriteSheet->reorderChild(__pEngine, -1);
		__pEngine->animNormal();
	}

	__pShield = new CShield();
	if(!__pShield->init(this, "shipShieldNormal", pSpriteSheet, pWorld))
	{
		return false;
	}
	else
	{
		// Change shield positioning anchor, so the shield bubble is covering the ship properly.
		float widthDifference = __pShield->boundingBox().size.width - this->boundingBox().size.width;
		CCPoint currentAnchor = __pShield->getAnchorPoint();
		float newX = currentAnchor.x - widthDifference / 2.0f / __pShield->boundingBox().size.width;
		CCPoint newAnchor(newX, currentAnchor.y);

		__pShield->setAnchorPoint(newAnchor);
	}

	return true;
}


void
CShip::setMove(cocos2d::CCPoint move)
{
	__move = move;
}

void
CShip::stopMoveActions()
{
	if(__pMoveAction)
	{
		this->stopAction(__pMoveAction);
	}

	if(__pEngineMoveAction)
	{
		__pEngine->stopAction(__pEngineMoveAction);
	}

	if(__pShield && __pShieldMoveAction)
	{
		__pShield->stopAction(__pShieldMoveAction);
	}
}

/**
 * Reduce the move vector coordinates to speedLimit.
 */
void
CShip::applySpeedLimit(const int speedLimit)
{
	if(__move.x > speedLimit)
	{
		__move.x = speedLimit;
	}
	else if(__move.x < -speedLimit)
	{
		__move.x = -speedLimit;
	}

	if(__move.y > speedLimit)
	{
		__move.y = speedLimit;
	}
	else if(__move.y < -speedLimit)
	{
		__move.y = -speedLimit;
	}
}

/**
 * Change the moveX and moveY values to 0 if they are too small.
 */
void
CShip::applyDeadZone(float* moveX, float* moveY)
{
	if(abs(*moveX * 10) < 5)
	{
		*moveX = 0;
	}

	if(abs(*moveY * 10) < 5)
	{
		*moveY = 0;
	}
}

/**
 * Limit the newPosition parameter to the screen area.
 */
void
CShip::limitMovementArea(CCPoint* newPosition)
{
	float hMargin = this->getContentSize().width / 2;
	float vMargin = this->getContentSize().height / 2;

	if(newPosition->x < hMargin)
	{
		newPosition->x = hMargin;
	}
	else if(newPosition->x > __winSize.width - hMargin)
	{
		newPosition->x = __winSize.width - hMargin;
	}

	if(newPosition->y < vMargin)
	{
		newPosition->y = vMargin;
	}
	else if(newPosition->y > __winSize.height - vMargin)
	{
		newPosition->y = __winSize.height - vMargin;
	}
}

void
CShip::runMoveActions(float moveDuration, CCPoint newPosition)
{
	__pMoveAction = CCMoveTo::actionWithDuration(moveDuration, newPosition);
	this->runAction(__pMoveAction);

	__pEngineMoveAction = (CCFiniteTimeAction*)__pMoveAction->copy()->autorelease();
	__pEngine->runAction(__pEngineMoveAction);

	if(__pShield)
	{
		__pShieldMoveAction	= (CCFiniteTimeAction*)__pMoveAction->copy()->autorelease();
		__pShield->runAction(__pShieldMoveAction);
	}
}

/**
 * Change the ship's animation according to the movement direction.
 */
void
CShip::changeMovementAnimation(float moveX)
{
	if(moveX > 7)
	{
		if(__currentShipDirection != Forward)
		{
			__currentShipDirection = Forward;
			__pEngine->animForward();
		}
	}
	else if(moveX < -7)
	{
		if(__currentShipDirection != Back)
		{
			__currentShipDirection = Back;
			__pEngine->animBack();
		}
	}
	else
	{
		if(__currentShipDirection != Normal)
		{
			__currentShipDirection = Normal;
			__pEngine->animNormal();
		}
	}
}

/**
 * Run the movement actions on the ship, the shield and the engine.
 */
void
CShip::move(float dt)
{
	if(__move.x == 0 && __move.y==0)
		return;
	const int speedLimit = 50;

	stopMoveActions();
	applySpeedLimit(speedLimit);

	// Multiplying by dt should improve movement smoothness.
	float moveX = __move.x * 1000 * dt;
	float moveY = __move.y * 1000 * dt;

	applyDeadZone(&moveX, &moveY);
	changeMovementAnimation(moveX);

	CCPoint currentPosition = this->getPosition();
	CCPoint newPosition = currentPosition;

	newPosition.x += moveX;
	newPosition.y += moveY;

	//Don't move too far
	limitMovementArea(&newPosition);

	//Calculate movement duration
	CCPoint moveDifference = ccpSub(currentPosition, newPosition);
	float distanceToMove = ccpLength(moveDifference);
	float moveDuration = distanceToMove / __speed;

	// Create the movement action
	runMoveActions(moveDuration, newPosition);
}

/**
 * Change the ship's sprite to indicate firing a bullet, create and fire the bullet.
 */
void
CShip::shoot()
{
	if(!__pRestoreSpriteAction)
	{
		const char* frameName = "shipFirstGun1.png";
		if(__bulletParams._type == CBulletParams::bomb)
		{
			frameName = "shipSecondGun1.png";
		}
		this->setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(frameName));
		__pRestoreSpriteAction = CCSequence::actions(CCDelayTime::actionWithDuration(0.2f),
													CCCallFunc::actionWithTarget(this, callfunc_selector(CShip::restoreSprite)),
													NULL);
		this->runAction(__pRestoreSpriteAction);
	}

	CBullet* pBullet = new CBullet();
	__pBullets->addObject(pBullet);
	pBullet->init("shipBullet", _pSpriteSheet, _pWorld);
	__bulletParams._startingPosition = _pBody->GetPosition();

	pBullet->fire(__bulletParams);
	if(__bulletParams._type == CBulletParams::standard)
	{
		CSoundPlayer::sharedPlayer().playStandardShot();
	}
	else
	{
		CSoundPlayer::sharedPlayer().playBombShot();
	}
}

/**
 * Restore the original ship's image.
 */
void
CShip::restoreSprite()
{
	this->setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("shipRoll1.png"));
	__pRestoreSpriteAction = NULL;
}

/**
 * Activates the invulnerability and plays the barrel roll animation.
 * Called after the player rapidly moves the device in direction of his head
 * (moves the screen upwards).
 */
void
CShip::doABarrelRoll()
{
	if(!__invulnerable)
	{
		__invulnerable = true;

		//Create animation
		CCAnimation* animation = CCAnimationCache::sharedAnimationCache()->animationByName("shipRoll");
		this->runAction(CCSequence::actionOneTwo(CCAnimate::actionWithAnimation(animation),
												 CCCallFunc::actionWithTarget(this, callfunc_selector(CShip::turnOffInvulnerability))));
	}
}

void
CShip::turnOffInvulnerability()
{
	__invulnerable = false;
}

/**
 * Reduce the ship's shield/health points and play adequate animation.
 *
 * @param damage This value will be subtracted from __currentShieldPoints or __currentHealthPoints.
 */
void
CShip::collision(float damage)
{
	if(!__invulnerable)
	{
		__currentShieldPoints -= damage;
		if(__pShield)
		{
			__pShield->hit();
		}
		if(__currentShieldPoints <= 0)
		{
			this->hit();
			__currentHealthPoints += __currentShieldPoints;
			__currentShieldPoints = 0;
			if(__pShield)
			{
				__pShield->setDelete(true);
			}

			if(__currentHealthPoints <= 0)
			{
				__currentHealthPoints = 0;
			}
		}

		float healthPoints = __currentHealthPoints / __healthPointsBase * 100.0f;
		float shieldPoints = __currentShieldPoints / __shieldPointsBase * 100.0f;
		CCScene* scene = CCDirector::sharedDirector()->getRunningScene();
		MainLayer* mainLayer = static_cast<MainLayer*>(scene->getChildByTag(GamePlayScene::mainLayerTag));
		if(healthPoints < 100)
		{
			mainLayer->updateHealthPoints(healthPoints);
		}

		if(shieldPoints >= 0)
		{
			mainLayer->updateShieldPoints(shieldPoints);
		}
	}
}

CCArray*
CShip::getBullets()
{
	return __pBullets;
}

/**
 * Run through the bullets and remove those that are off the screen
 * or that return true in getDelete() method.
 */
void
CShip::cleanBullets()
{
	CCArray* pBulletsToRemove = new CCArray();
	CCObject* bulletIter = NULL;
	CCARRAY_FOREACH(__pBullets,bulletIter)
	{
		CBullet* pBullet = (CBullet*)bulletIter;
		if(!pBullet)
		{
			break;
		}

		CCRect winRect = CCRect(-30.0f, -30.0f, __winSize.width + 30, __winSize.height + 100);
		CCRect bulletRect = pBullet->getRect();
		if(!CCRect::CCRectIntersectsRect(winRect, bulletRect))
		{
			// The bullet is off screen.
			pBullet->setDelete(true);
		}

        bool removeBullet = pBullet->getDelete();
        if(removeBullet)
        {
        	pBullet->destroy();
        	pBulletsToRemove->addObject(pBullet);
        }
	}

    __pBullets->removeObjectsInArray(pBulletsToRemove);
    delete pBulletsToRemove;
}

/**
 * Check if the bullet is colliding with pEnemy.
 */
void
CShip::checkBulletCollisions(CEnemy* pEnemy)
{
	CCRect enemyRect = pEnemy->getRect();
	CCObject* bulletIter;
	CCARRAY_FOREACH(__pBullets,bulletIter)
	{
		CBullet* pBullet = (CBullet*)bulletIter;
		CCRect bulletRect = pBullet->getRect();
		bool collisionWithEnemy = CCRect::CCRectIntersectsRect(bulletRect, enemyRect);
		if(collisionWithEnemy)
		{
			pBullet->setDelete(true);
			pEnemy->collision(pBullet->getDamage());
		}
	}

	cleanBullets();
}

void
CShip::cleanShield()
{
	if((__pShield) && (__pShield->getDelete()))
	{
		__pShield->destroy();
		__pShield->release();
		__pShield = NULL;
	}
}

/**
 * This method will return the shields rectangle
 * if the ship still has it.
 */
cocos2d::CCRect
CShip::getRect()
{
	if(__pShield)
	{
		return __pShield->getRect();
	}
	else
	{
		return CAnimatedObject::getRect();
	}
}

float
CShip::getCollisionDamage()
{
	return __currentHealthPoints + __currentShieldPoints;
}

float
CShip::getCurrentHealthPoints()
{
	return __currentHealthPoints;
}

/**
 * Flash the ship and restore the original frame.
 */
void
CShip::hit()
{
	this->setDisplayFrame(CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName("shipRed1.png"));
	this->runAction(CCSequence::actions(CCDelayTime::actionWithDuration(0.7f),
					CCCallFunc::actionWithTarget(this, callfunc_selector(CShip::restoreSprite)),
					NULL));
}

void
CShip::changeGunType()
{
	if(__bulletParams._type == CBulletParams::standard)
	{
		__bulletParams._type = CBulletParams::bomb;
	}
	else
	{
		__bulletParams._type = CBulletParams::standard;
	}
}

void
CShip::createBoxBody()
{
    b2BodyDef spriteBodyDef;
    spriteBodyDef.type = b2_dynamicBody;
    float x = this->getPosition().x / P2M;
    float y = this->getPosition().y / P2M;
    spriteBodyDef.position.Set(x, y);
    spriteBodyDef.userData = this;
    _pBody = _pWorld->CreateBody(&spriteBodyDef);

    b2PolygonShape spriteShape;
    int num = 6;
    b2Vec2 verts[] = {
    		b2Vec2(-27.1305f / P2M, -10.3090f / P2M),
    		b2Vec2(24.7788f / P2M, -8.6346f / P2M),
    		b2Vec2(41.8023f / P2M, -4.1692f / P2M),
    		b2Vec2(25.6158f / P2M, 3.9245f / P2M),
    		b2Vec2(7.4754f / P2M, 11.1804f / P2M),
    		b2Vec2(-19.0368f / P2M, 14.5292f / P2M)
    };

    spriteShape.Set(verts, num);

    b2FixtureDef spriteShapeDef;
    spriteShapeDef.shape = &spriteShape;
    spriteShapeDef.density = 1.0;
    spriteShapeDef.restitution = 0.5f;
    spriteShapeDef.filter.categoryBits = ECategoryBit::shipCategoryBit;
    spriteShapeDef.filter.maskBits = ECategoryBit::enemyCategoryBit;

    _pBody->CreateFixture(&spriteShapeDef);
}

float
CShip::getBulletInterval()
{
	switch(__bulletParams._type)
	{
	case CBulletParams::standard:
		return __standardGunInterval;

	case CBulletParams::bomb:
		return __bombGunInterval;
	}
	return 0;
}
